package ucl.cs.stockbroker.server;

/**
 * StockBrokerAccount Implementation.
 * @author  Min Park, Saniul Ahmed, Kamil Olesiejuk
 * @version 1.0
 */
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import ucl.cs.stockbroker.common.*;

public class StockBrokerAccountImpl extends java.rmi.server.UnicastRemoteObject
		implements StockBrokerAccount, Comparable<StockBrokerAccount> {

	// This supports serialization.
	static final long serialVersionUID = 1;

	private final String name;
	private final String pin;
	private final int accountNumber;
	private double balance = 0;
	private final StockBroker broker;
	private final double MAX_LOAN = -1000000;

	private ArrayList<StockCompanyInfo> registeredStock;
	private final Map<String, Integer> ownedStock = new ConcurrentHashMap<String, Integer>();

	public StockBrokerAccountImpl(String name, int accountNumber, String pin,
			StockBroker broker) throws RemoteException {
		super();
		this.name = name;
		this.accountNumber = accountNumber;
		this.pin = pin;
		this.broker = broker;
	}

	public synchronized void buyStock(String pin, String compan, int units)
			throws RemoteException, WrongPINException, NotEnoughMoneyException,
			NoSuchCompanyException {
		String company = compan.toUpperCase();
		if (units == 0)
			return; // When the input is 0
		if (this.isPINValid(pin) == true) // if false, gives error
		{
			this.registeredStock = this.broker.getRegisteredStock();
			boolean companyFound = false;
			for (final StockCompanyInfo comp : this.registeredStock) {
				if (comp.getCompanySymbol().equalsIgnoreCase(company)) { // Check
					companyFound = true; // whether
					// the
					// company is in the
					// list

					final double companyPrice = comp.getStockPrice(); // Get the
					// price
					// of the stock
					System.out.println(this.name + ": Buying " + units
							+ " of stock units from " + company); // DEBUG

					if (this.ownedStock.containsKey(company) == false) { // Check
						// if
						// the
						// user has its
						// stock already
						final double total = units * companyPrice;

						if (this.balance - total > MAX_LOAN) { // If the
							// balance
							// reaches
							// -10000, it gives
							// error

							this.ownedStock.put(company, units);
							this.threadRest1();
							this.balance -= total;

						} else
							throw new NotEnoughMoneyException(
									);
						// System.out.println("You do not have enough money
						// to
						// purchase the stock");
					}

					else {
						final double total = units * companyPrice;

						if (this.balance - total > MAX_LOAN) {

							int ownedAmount = this.ownedStock.get(company);
							ownedAmount += units;
							this.ownedStock.put(company, ownedAmount); // need
							// to
							// check
							// whether it
							// overwrites the
							// value or not
							this.threadRest1();
							this.balance -= total;
						} else
							throw new NotEnoughMoneyException(
									);
						// System.out.println("You do not have enough money
						// to
						// purchase the stock");
					}
				}
			}
			if (!companyFound)
				throw new NoSuchCompanyException();
			// System.out.println("Invalid company name");
		} else
			throw new WrongPINException();
		// System.out.println("Invalid PIN number, please try
		// again");
	}

	public int getAccountNumber() {
		return this.accountNumber;
	}

	public double getBalance() {
		return this.balance;
	}

	public StockBrokerAccountInfo getDescription() throws RemoteException {
		return new StockBrokerAccountInfo(this.name, this.accountNumber,
				this.balance, calculateAccountValue(), this.ownedStock);
	}

	public String getName() {
		return this.name;
	}

	public Map<String, Integer> getOwnedStock() throws RemoteException {
		return this.ownedStock;
	}

	public boolean isPINValid(String pin) throws RemoteException {
		if (this.pin.equalsIgnoreCase(pin)) {
			return true;
		} else {
			return false;
		}
	}

	public synchronized void sellStock(String pin, String company)
			throws RemoteException, WrongPINException, NoSuchCompanyException,
			NotEnoughStockException {
		this.sellStock(pin, company, this.ownedStock.get(company));
	}

	public synchronized void sellStock(String pin, String compan, int units)
			throws RemoteException, WrongPINException, NoSuchCompanyException,
			NotEnoughStockException {
		String company = compan.toUpperCase();
		if (units == 0)
			return; // When the input is 0
		if (this.isPINValid(pin) == true) {

			this.registeredStock = this.broker.getRegisteredStock();
			boolean companyFound = false;
			for (final StockCompanyInfo comp : this.registeredStock)
				if (comp.getCompanySymbol().equalsIgnoreCase(company)) { // Check
					// whether
					// the
					// company is in the
					// list
					companyFound = true;
					final double companyPrice = comp.getStockPrice(); // Get the
					// price
					// of the stock
					System.out.println(this.name + ": Selling " + units
							+ " of stock units of " + company); // DEBUG

					if (this.ownedStock.containsKey(company) == true) { // Check
						// if
						// the user
						// has its stock
						// already

						final int temp = this.ownedStock.get(company) - units;
						if (temp >= 0) // Check if there are enough units to
						// sell
						{

							final double UnitsAmount = units * companyPrice;
							this.threadRest1();
							this.balance += UnitsAmount;
							if (temp == 0)
								// remove the record if sold all shares
								this.ownedStock.remove(company);
							else
								this.ownedStock.put(company, temp);
						} else
							throw new NotEnoughStockException(
									);
						// System.out.println("Not enough stock units");
					} else
						throw new NotEnoughStockException(
								);
					// System.out.println("You don't have right company on
					// your list");
				}
			if (!companyFound)
				throw new NoSuchCompanyException();
			// System.out.println("Invalid company name");
		} else
			throw new WrongPINException();
		// System.out.println("Invalid PIN number, please try again");
	}

	private void threadRest1() {
		try {
			Thread.sleep(5000);
		} catch (final InterruptedException except) {
		}
	}

	public double calculateAccountValue() {
		double temp = balance;
		try {
			this.registeredStock = broker.getRegisteredStock();
		} catch (RemoteException e) {
			//e.printStackTrace();
		}
		for (String company : ownedStock.keySet()) {
			for (StockCompanyInfo compInfo : registeredStock) {
				if (company.equalsIgnoreCase(compInfo.getName()))
					temp += ownedStock.get(company) * compInfo.getStockPrice();
			}
		}
		return temp;
	}

	public int compareTo(StockBrokerAccount account) {
		if (!(account instanceof StockBrokerAccountImpl))
			throw new ClassCastException(
					"A StockBrokerAccountImpl object expected.");
		double otherTotalValue = ((StockBrokerAccountImpl) account)
				.calculateAccountValue();
		return (int) ( otherTotalValue - this.calculateAccountValue());
	}

}
